package org.openbpm.sys.aop;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.caucho.hessian.io.AnnotationInvocationHandler;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.openbpm.base.core.id.IdUtil;
import org.openbpm.base.core.util.JsonUtil;
import org.openbpm.base.core.util.StringUtil;
import org.openbpm.base.rest.util.RequestUtil;
import org.openbpm.sys.core.manager.SysOperateLogManager;
import org.openbpm.sys.core.manager.SysOperateLogMetaManager;
import org.openbpm.sys.core.model.SysOperateLog;
import org.openbpm.sys.core.model.SysOperateLogMetadata;
import org.openbpm.sys.util.ContextUtil;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.util.Date;
import java.util.List;

@Aspect
@Component
@Slf4j
public class OperateLogAspect {

    // 需要被SpEl解析的模板前缀和后缀 #{ expression  }
    public static final TemplateParserContext templateParserContext = new TemplateParserContext("#{", "}");
    // SpEL解析器
    public static final ExpressionParser spelExpressionParser = new SpelExpressionParser();

    @Pointcut("execution(* org.openbpm..*Controller.*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void operateService() {
    }

    @Resource
    private SysOperateLogMetaManager operateLogMetaManager;

    @Resource
    private SysOperateLogManager operateLogManager;

    @Around("operateService()")
    public Object doOperateLog (ProceedingJoinPoint joinPoint) throws Throwable {
        Object returnVal = null;
        try {
            returnVal = joinPoint.proceed();
            try{
                doOperateLog(joinPoint, returnVal);
            }catch (Exception e){
                log.error(e.getMessage());
            }
        } catch (Exception ex) {
            throw ex;
        } finally {
            //
        }
        return  returnVal;
    }

    private void doOperateLog(ProceedingJoinPoint joinPoint, Object returnVal){
        // check request
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        if(request==null){
            return;
        }

        // check @RestController
        if(joinPoint instanceof MethodInvocationProceedingJoinPoint){
            Object h =  joinPoint.getTarget().getClass().getAnnotation(org.springframework.web.bind.annotation.RestController.class);
            if(h==null){
                return;
            }
        }

        // check by path
        String path = request.getRequestURI();
        List<SysOperateLogMetadata> metadataList = operateLogMetaManager.loadMetaListByPath(path);
        if(CollectionUtil.isEmpty(metadataList)){
            return;
        }

        // prepare context
        EvaluationContext evaluationContext = new StandardEvaluationContext();
        // currentUser      #currentUser.getFullname()
        evaluationContext.setVariable("currentUser", ContextUtil.getCurrentUser());
        // 参数 requestParam     #requestParam["id"][0], #requestParam["account"][0]
        evaluationContext.setVariable("requestParam", request.getParameterMap());
        // requestBody      #requestBody["id"]), #requestBody["name"]
        // 参数 RequestBody
        Object[] args = joinPoint.getArgs();
        if(args!=null && args.length>0){
            evaluationContext.setVariable("requestBody", args[0]);
        }
        // responseBody     #{#responseBody.data}
        evaluationContext.setVariable("responseBody", returnVal);

        //how to support method isEmpty ?
        try {
            ((StandardEvaluationContext)evaluationContext).registerFunction("isEmpty",
                    ObjectUtil.class.getDeclaredMethod("isEmpty",Object.class));
        } catch (NoSuchMethodException e) {
            log.warn(e.getMessage());
        }

        // operate log
        for(SysOperateLogMetadata metadata: metadataList){
            try{
                String predicateExpr = metadata.getPredicateExpr();
                Boolean predicateResult = spelExpressionParser.parseExpression(predicateExpr, templateParserContext).getValue(evaluationContext, Boolean.class);
                if(predicateResult==null || !predicateResult){
                    continue;
                }
            }catch (Exception e){
                log.warn(e.getMessage());
                continue;
            }
            try{
                SysOperateLog operateLog = new SysOperateLog();
                operateLog.setId(IdUtil.getSuid());
                operateLog.setLogMetadataId(metadata.getId());
                operateLog.setAccount(ContextUtil.getCurrentUserAccount());
                operateLog.setOperator(ContextUtil.getCurrentUser().getFullname());
                operateLog.setIp(RequestUtil.getIpAddr(request));
                operateLog.setTraceId(operateLog.getId()); //traceId need update
                if(StringUtil.isNotEmpty(metadata.getBizIdExpr())){
                    try{
                        operateLog.setBizId(spelExpressionParser.parseExpression(metadata.getBizIdExpr(), templateParserContext).getValue(evaluationContext, String.class));
                    }catch (Exception e){
                        log.warn(e.getMessage());
                    }
                }
                if(StringUtil.isNotEmpty(metadata.getDescriptionTpl())){
                    try{
                        operateLog.setDescription(spelExpressionParser.parseExpression(metadata.getDescriptionTpl(), templateParserContext).getValue(evaluationContext, String.class));
                    }catch (Exception e){
                        log.warn(e.getMessage());
                    }
                }
                if(StringUtil.isNotEmpty(metadata.getDataExpr())){
                    try{
                        operateLog.setData(JsonUtil.toJSONString(spelExpressionParser.parseExpression(metadata.getDataExpr(), templateParserContext).getValue(evaluationContext)));
                    }catch (Exception e){
                        log.warn(e.getMessage());
                    }
                }
                operateLog.setCreateTime(new Date());
                operateLogManager.save(operateLog);
            }catch (Exception e){
                log.warn(e.getMessage());
            }

        }
    }

}
